﻿using System;
using System.Globalization;
using System.IO;
using System.Text;
using GeoAPI.Geometries;
using GeoAPI.IO;
using YJKGridTopologyCal.Utilities;

namespace YJKGridTopologyCal.IO
{
	public class WKTWriter : ITextGeometryWriter, IGeometryWriter<string>, IGeometryIOSettings
	{
		public static string ToPoint(Coordinate p0)
		{
			return string.Format(CultureInfo.InvariantCulture, "POINT({0} {1})", new object[]
			{
				p0.X,
				p0.Y
			});
		}

		public static string ToLineString(ICoordinateSequence seq)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("LINESTRING");
			if (seq.Count == 0)
			{
				stringBuilder.Append(" EMPTY");
			}
			else
			{
				stringBuilder.Append("(");
				for (int i = 0; i < seq.Count; i++)
				{
					if (i > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(string.Format(CultureInfo.InvariantCulture, "{0} {1}", new object[]
					{
						seq.GetX(i),
						seq.GetY(i)
					}));
				}
				stringBuilder.Append(")");
			}
			return stringBuilder.ToString();
		}

		public static string ToLineString(Coordinate[] coord)
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("LINESTRING ");
			if (coord.Length == 0)
			{
				stringBuilder.Append(" EMPTY");
			}
			else
			{
				stringBuilder.Append("(");
				for (int i = 0; i < coord.Length; i++)
				{
					if (i > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(coord[i].X + " " + coord[i].Y);
				}
				stringBuilder.Append(")");
			}
			return stringBuilder.ToString();
		}

		public static string ToLineString(Coordinate p0, Coordinate p1)
		{
			return string.Format(CultureInfo.InvariantCulture, "LINESTRING({0:R} {1:R}, {2:R} {3:R})", new object[]
			{
				p0.X,
				p0.Y,
				p1.X,
				p1.Y
			});
		}

		internal static NumberFormatInfo CreateFormatter(IPrecisionModel precisionModel)
		{
			int maximumSignificantDigits = precisionModel.MaximumSignificantDigits;
			int numberDecimalDigits = Math.Max(0, maximumSignificantDigits);
			return new NumberFormatInfo
			{
				NumberDecimalSeparator = ".",
				NumberDecimalDigits = numberDecimalDigits,
				NumberGroupSeparator = string.Empty,
				NumberGroupSizes = new int[0]
			};
		}

		public static string StringOfChar(char ch, int count)
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < count; i++)
			{
				stringBuilder.Append(ch);
			}
			return stringBuilder.ToString();
		}

		public WKTWriter()
		{
		}

		public WKTWriter(int outputDimension)
		{
			if (outputDimension < 2 || outputDimension > 3)
			{
				throw new ArgumentException("Output dimension must be in the range [2, 3]", "outputDimension");
			}
			this._outputDimension = outputDimension;
		}

		public bool Formatted
		{
			get
			{
				return this._isFormatted;
			}
			set
			{
				this._isFormatted = value;
			}
		}

		public int MaxCoordinatesPerLine
		{
			get
			{
				return this._coordsPerLine;
			}
			set
			{
				this._coordsPerLine = value;
			}
		}

		public int Tab
		{
			get
			{
				return this._indentTabStr.Length;
			}
			set
			{
				if (value <= 0)
				{
					throw new ArgumentException("Tab count must be positive", "value");
				}
				this._indentTabStr = WKTWriter.StringOfChar(' ', value);
			}
		}

		public bool EmitSRID { get; set; }

		public bool EmitZ { get; set; }

		public bool EmitM { get; set; }

		public virtual string Write(IGeometry geometry)
		{
			StringBuilder stringBuilder = new StringBuilder();
			TextWriter sw = new StringWriter(stringBuilder);
			this.TryWrite(geometry, sw);
			return stringBuilder.ToString();
		}

		public void Write(IGeometry geometry, Stream stream)
		{
			TextWriter sw = new StreamWriter(stream);
			this.TryWrite(geometry, sw);
		}

		private void TryWrite(IGeometry geometry, TextWriter sw)
		{
			try
			{
				this.WriteFormatted(geometry, this._isFormatted, sw);
			}
			catch (IOException)
			{
				Assert.ShouldNeverReachHere();
			}
		}

		public virtual void Write(IGeometry geometry, TextWriter writer)
		{
			this.WriteFormatted(geometry, this._isFormatted, writer);
		}

		public virtual string WriteFormatted(IGeometry geometry)
		{
			TextWriter textWriter = new StringWriter();
			try
			{
				this.WriteFormatted(geometry, true, textWriter);
			}
			catch (IOException)
			{
				Assert.ShouldNeverReachHere();
			}
			return textWriter.ToString();
		}

		public virtual void WriteFormatted(IGeometry geometry, TextWriter writer)
		{
			this.WriteFormatted(geometry, true, writer);
		}

		private void WriteFormatted(IGeometry geometry, bool useFormatting, TextWriter writer)
		{
			if (geometry == null)
			{
				throw new System.ArgumentNullException("geometry");
			}
			this._useFormating = useFormatting;
			IPrecisionModel precisionModel = geometry.Factory.PrecisionModel;
			this._useMaxPrecision = (precisionModel.PrecisionModelType == PrecisionModels.Floating);
			this._formatter = WKTWriter.CreateFormatter(geometry.PrecisionModel);
			this._format = "0." + WKTWriter.StringOfChar('#', this._formatter.NumberDecimalDigits);
			this.AppendGeometryTaggedText(geometry, 0, writer);
			this._useMaxPrecision = false;
		}

		private void AppendGeometryTaggedText(IGeometry geometry, int level, TextWriter writer)
		{
			this.Indent(level, writer);
			if (geometry is IPoint)
			{
				IPoint point = (IPoint)geometry;
				this.AppendPointTaggedText(point.Coordinate, level, writer, point.PrecisionModel);
				return;
			}
			if (geometry is ILinearRing)
			{
				this.AppendLinearRingTaggedText((ILinearRing)geometry, level, writer);
				return;
			}
			if (geometry is ILineString)
			{
				this.AppendLineStringTaggedText((ILineString)geometry, level, writer);
				return;
			}
			if (geometry is IPolygon)
			{
				this.AppendPolygonTaggedText((IPolygon)geometry, level, writer);
				return;
			}
			if (geometry is IMultiPoint)
			{
				this.AppendMultiPointTaggedText((IMultiPoint)geometry, level, writer);
				return;
			}
			if (geometry is IMultiLineString)
			{
				this.AppendMultiLineStringTaggedText((IMultiLineString)geometry, level, writer);
				return;
			}
			if (geometry is IMultiPolygon)
			{
				this.AppendMultiPolygonTaggedText((IMultiPolygon)geometry, level, writer);
				return;
			}
			if (geometry is IGeometryCollection)
			{
				this.AppendGeometryCollectionTaggedText((IGeometryCollection)geometry, level, writer);
				return;
			}
			Assert.ShouldNeverReachHere("Unsupported Geometry implementation:" + geometry.GetType());
		}

		private void AppendPointTaggedText(Coordinate coordinate, int level, TextWriter writer, IPrecisionModel precisionModel)
		{
			writer.Write("POINT ");
			this.AppendPointText(coordinate, level, writer, precisionModel);
		}

		private void AppendLineStringTaggedText(ILineString lineString, int level, TextWriter writer)
		{
			writer.Write("LINESTRING ");
			this.AppendLineStringText(lineString, level, false, writer);
		}

		private void AppendLinearRingTaggedText(ILinearRing linearRing, int level, TextWriter writer)
		{
			writer.Write("LINEARRING ");
			this.AppendLineStringText(linearRing, level, false, writer);
		}

		private void AppendPolygonTaggedText(IPolygon polygon, int level, TextWriter writer)
		{
			writer.Write("POLYGON ");
			this.AppendPolygonText(polygon, level, false, writer);
		}

		private void AppendMultiPointTaggedText(IMultiPoint multipoint, int level, TextWriter writer)
		{
			writer.Write("MULTIPOINT ");
			this.AppendMultiPointText(multipoint, level, writer);
		}

		private void AppendMultiLineStringTaggedText(IMultiLineString multiLineString, int level, TextWriter writer)
		{
			writer.Write("MULTILINESTRING ");
			this.AppendMultiLineStringText(multiLineString, level, false, writer);
		}

		private void AppendMultiPolygonTaggedText(IMultiPolygon multiPolygon, int level, TextWriter writer)
		{
			writer.Write("MULTIPOLYGON ");
			this.AppendMultiPolygonText(multiPolygon, level, writer);
		}

		private void AppendGeometryCollectionTaggedText(IGeometryCollection geometryCollection, int level, TextWriter writer)
		{
			writer.Write("GEOMETRYCOLLECTION ");
			this.AppendGeometryCollectionText(geometryCollection, level, writer);
		}

		private void AppendPointText(Coordinate coordinate, int level, TextWriter writer, IPrecisionModel precisionModel)
		{
			if (coordinate == null)
			{
				writer.Write("EMPTY");
				return;
			}
			writer.Write("(");
			this.AppendCoordinate(coordinate, writer, precisionModel);
			writer.Write(")");
		}

		private void AppendCoordinate(ICoordinateSequence seq, int i, TextWriter writer)
		{
			writer.Write(this.WriteNumber(seq.GetX(i)) + " " + this.WriteNumber(seq.GetY(i)));
			if (this._outputDimension >= 3 && seq.Dimension >= 3)
			{
				double ordinate = seq.GetOrdinate(i, Ordinate.Z);
				if (!double.IsNaN(ordinate))
				{
					writer.Write(" ");
					writer.Write(this.WriteNumber(ordinate));
				}
			}
		}

		private void AppendCoordinate(Coordinate coordinate, TextWriter writer, IPrecisionModel precisionModel)
		{
			writer.Write(this.WriteNumber(coordinate.X) + " " + this.WriteNumber(coordinate.Y));
			if (this._outputDimension >= 3 && !double.IsNaN(coordinate.Z))
			{
				writer.Write(" " + this.WriteNumber(coordinate.Z));
			}
		}

		private string WriteNumber(double d)
		{
			string text = d.ToString(this._format, this._formatter);
			if (!this._useMaxPrecision)
			{
				return text;
			}
			string result;
			try
			{
				if (Convert.ToDouble(text, this._formatter) == d)
				{
					result = text;
				}
				else
				{
					result = string.Format(this._formatter, "{0:R}", new object[]
					{
						d
					});
				}
			}
			catch (OverflowException)
			{
				result = string.Format(this._formatter, "{0:R}", new object[]
				{
					d
				});
			}
			return result;
		}

		private void AppendSequenceText(ICoordinateSequence seq, int level, bool doIndent, TextWriter writer)
		{
			if (seq.Count == 0)
			{
				writer.Write("EMPTY");
				return;
			}
			if (doIndent)
			{
				this.Indent(level, writer);
			}
			writer.Write("(");
			for (int i = 0; i < seq.Count; i++)
			{
				if (i > 0)
				{
					writer.Write(", ");
					if (this._coordsPerLine > 0 && i % this._coordsPerLine == 0)
					{
						this.Indent(level + 1, writer);
					}
				}
				this.AppendCoordinate(seq, i, writer);
			}
			writer.Write(")");
		}

		private void AppendLineStringText(ILineString lineString, int level, bool doIndent, TextWriter writer)
		{
			if (lineString.IsEmpty)
			{
				writer.Write("EMPTY");
				return;
			}
			if (doIndent)
			{
				this.Indent(level, writer);
			}
			writer.Write("(");
			for (int i = 0; i < lineString.NumPoints; i++)
			{
				if (i > 0)
				{
					writer.Write(", ");
					if (this._coordsPerLine > 0 && i % this._coordsPerLine == 0)
					{
						this.Indent(level + 1, writer);
					}
				}
				this.AppendCoordinate(lineString.GetCoordinateN(i), writer, lineString.PrecisionModel);
			}
			writer.Write(")");
		}

		private void AppendPolygonText(IPolygon polygon, int level, bool indentFirst, TextWriter writer)
		{
			if (polygon.IsEmpty)
			{
				writer.Write("EMPTY");
				return;
			}
			if (indentFirst)
			{
				this.Indent(level, writer);
			}
			writer.Write("(");
			this.AppendLineStringText(polygon.ExteriorRing, level, false, writer);
			for (int i = 0; i < polygon.NumInteriorRings; i++)
			{
				writer.Write(", ");
				this.AppendLineStringText(polygon.GetInteriorRingN(i), level + 1, true, writer);
			}
			writer.Write(")");
		}

		private void AppendMultiPointText(IMultiPoint multiPoint, int level, TextWriter writer)
		{
			if (multiPoint.IsEmpty)
			{
				writer.Write("EMPTY");
				return;
			}
			writer.Write("(");
			for (int i = 0; i < multiPoint.NumGeometries; i++)
			{
				if (i > 0)
				{
					writer.Write(", ");
					this.IndentCoords(i, level + 1, writer);
				}
				writer.Write("(");
				this.AppendCoordinate(multiPoint.GetGeometryN(i).Coordinate, writer, multiPoint.PrecisionModel);
				writer.Write(")");
			}
			writer.Write(")");
		}

		private void AppendMultiLineStringText(IMultiLineString multiLineString, int level, bool indentFirst, TextWriter writer)
		{
			if (multiLineString.IsEmpty)
			{
				writer.Write("EMPTY");
				return;
			}
			int level2 = level;
			bool doIndent = indentFirst;
			writer.Write("(");
			for (int i = 0; i < multiLineString.NumGeometries; i++)
			{
				if (i > 0)
				{
					writer.Write(", ");
					level2 = level + 1;
					doIndent = true;
				}
				this.AppendLineStringText((ILineString)multiLineString.GetGeometryN(i), level2, doIndent, writer);
			}
			writer.Write(")");
		}

		private void AppendMultiPolygonText(IMultiPolygon multiPolygon, int level, TextWriter writer)
		{
			if (multiPolygon.IsEmpty)
			{
				writer.Write("EMPTY");
				return;
			}
			int level2 = level;
			bool indentFirst = false;
			writer.Write("(");
			for (int i = 0; i < multiPolygon.NumGeometries; i++)
			{
				if (i > 0)
				{
					writer.Write(", ");
					level2 = level + 1;
					indentFirst = true;
				}
				this.AppendPolygonText((IPolygon)multiPolygon.GetGeometryN(i), level2, indentFirst, writer);
			}
			writer.Write(")");
		}

		private void AppendGeometryCollectionText(IGeometryCollection geometryCollection, int level, TextWriter writer)
		{
			if (geometryCollection.IsEmpty)
			{
				writer.Write("EMPTY");
				return;
			}
			int level2 = level;
			writer.Write("(");
			for (int i = 0; i < geometryCollection.NumGeometries; i++)
			{
				if (i > 0)
				{
					writer.Write(", ");
					level2 = level + 1;
				}
				this.AppendGeometryTaggedText(geometryCollection.GetGeometryN(i), level2, writer);
			}
			writer.Write(")");
		}

		private void IndentCoords(int coordIndex, int level, TextWriter writer)
		{
			if (this._coordsPerLine <= 0 || coordIndex % this._coordsPerLine != 0)
			{
				return;
			}
			this.Indent(level, writer);
		}

		private void Indent(int level, TextWriter writer)
		{
			if (!this._useFormating || level <= 0)
			{
				return;
			}
			writer.Write("\n");
			for (int i = 0; i < level; i++)
			{
				writer.Write(this._indentTabStr);
			}
		}

		public bool HandleSRID
		{
			get
			{
				return this.EmitSRID;
			}
			set
			{
				this.EmitSRID = value;
			}
		}

		public Ordinates AllowedOrdinates
		{
			get
			{
				return Ordinates.XYZM;
			}
		}

		public Ordinates HandleOrdinates
		{
			get
			{
				Ordinates ordinates = Ordinates.XY;
				if (this.EmitZ)
				{
					ordinates |= Ordinates.Z;
				}
				if (this.EmitM)
				{
					ordinates |= Ordinates.M;
				}
				return ordinates;
			}
			set
			{
				value &= this.AllowedOrdinates;
				if ((value & Ordinates.Z) != Ordinates.None)
				{
					this.EmitZ = true;
				}
				if ((value & Ordinates.M) != Ordinates.None)
				{
					this.EmitM = true;
				}
			}
		}

		private readonly int _outputDimension = 2;

		private const string MaxPrecisionFormat = "{0:R}";

		private NumberFormatInfo _formatter;

		private string _format;

		private bool _isFormatted;

		private bool _useFormating;

		private bool _useMaxPrecision;

		private int _coordsPerLine = -1;

		private string _indentTabStr = "  ";
	}
}
